/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/**
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XMLMapperBuilder extends BaseBuilder {

  //XPathParser进行总控
  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(reader, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(inputStream, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

  //加载的基本逻辑和加载mybatis-config一样的过程，使用XPathParser进行总控，XMLMapperEntityResolver进行具体判断
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

  public XNode getSqlFragment(String refid) {
    return sqlFragments.get(refid);
  }

  //解析mapper的核心
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);

      /**
       * 解析缓存参照cache-ref <cache-ref namespace=”com.someone.application.data.SomeMapper”/>
       *
       * 缓存参考因为通过namespace指向其他的缓存。所以会出现第一次解析的时候指向的缓存还不存在的情况，
       * 所以需要在所有的mapper文件加载完成后进行二次处理，不仅仅是缓存参考，其他的CRUD也一样。
       * 所以在XMLMapperBuilder.configuration中有很多的incompleteXXX，这种设计模式类似于JVM GC中的mark and sweep，
       * 标记、然后处理。所以当捕获到IncompleteElementException异常时，没有终止执行，
       * 而是将指向的缓存不存在的cacheRefResolver添加到configuration.incompleteCacheRef中
       */
      cacheRefElement(context.evalNode("cache-ref"));
      /**
       * 解析缓存cache
       *
       * <!ELEMENT cache (property*)>
       * <!ATTLIST cache
       * type CDATA #IMPLIED
       * eviction CDATA #IMPLIED
       * flushInterval CDATA #IMPLIED
       * size CDATA #IMPLIED
       * readOnly CDATA #IMPLIED
       * blocking CDATA #IMPLIED
       * >
       *
       * 默认情况下，mybatis使用的是永久缓存PerpetualCache，读取或设置各个属性默认值之后，
       * 调用builderAssistant.useNewCache构建缓存，其中的CacheBuilder使用了build模式
       * （在effective里面，建议有4个以上可选属性时，应该为对象提供一个builder便于使用），
       * 只要实现org.apache.ibatis.cache.Cache接口，就是合法的mybatis缓存。
       * 我们先来看下缓存的DTD定义：
       */
      cacheElement(context.evalNode("cache"));
      /**
       * 解析参数映射parameterMap
       *
       * <!ELEMENT parameterMap (parameter+)?>
       * <!ATTLIST parameterMap
       * id CDATA #REQUIRED
       * type CDATA #REQUIRED
       * >
       *
       * <!ELEMENT parameter EMPTY>
       * <!ATTLIST parameter
       * property CDATA #REQUIRED
       * javaType CDATA #IMPLIED
       * jdbcType CDATA #IMPLIED
       * mode (IN | OUT | INOUT) #IMPLIED
       * resultMap CDATA #IMPLIED
       * scale CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * >
       *
       * 目前已经不推荐使用参数映射，而是直接使用内联参数。所以我们这里就不展开细讲了。如有必要，我们后续会补上
       */
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));

      /**
       * 解析结果集映射resultMap
       *
       * 结果集映射早期版本可以说是用的最多的辅助节点了，不过有了mapUnderscoreToCamelCase属性之后，
       * 如果命名规范控制做的好的话，resultMap也是可以省略的。
       * 每个mapper文件可以有多个结果集映射。最终来说，它还是使用频率很高的。
       *
       * 我们先来看下DTD定义：
       *
       * <!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
       * <!ATTLIST resultMap
       * id CDATA #REQUIRED
       * type CDATA #REQUIRED
       * extends CDATA #IMPLIED
       * autoMapping (true|false) #IMPLIED
       * >
       *
       * <!ELEMENT constructor (idArg*,arg*)>
       *
       * <!ELEMENT id EMPTY>
       * <!ATTLIST id
       * property CDATA #IMPLIED
       * javaType CDATA #IMPLIED
       * column CDATA #IMPLIED
       * jdbcType CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * >
       *
       * <!ELEMENT result EMPTY>
       * <!ATTLIST result
       * property CDATA #IMPLIED
       * javaType CDATA #IMPLIED
       * column CDATA #IMPLIED
       * jdbcType CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * >
       *
       * <!ELEMENT idArg EMPTY>
       * <!ATTLIST idArg
       * javaType CDATA #IMPLIED
       * column CDATA #IMPLIED
       * jdbcType CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * select CDATA #IMPLIED
       * resultMap CDATA #IMPLIED
       * name CDATA #IMPLIED
       * >
       *
       * <!ELEMENT arg EMPTY>
       * <!ATTLIST arg
       * javaType CDATA #IMPLIED
       * column CDATA #IMPLIED
       * jdbcType CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * select CDATA #IMPLIED
       * resultMap CDATA #IMPLIED
       * name CDATA #IMPLIED
       * >
       *
       * <!ELEMENT collection (constructor?,id*,result*,association*,collection*, discriminator?)>
       * <!ATTLIST collection
       * property CDATA #REQUIRED
       * column CDATA #IMPLIED
       * javaType CDATA #IMPLIED
       * ofType CDATA #IMPLIED
       * jdbcType CDATA #IMPLIED
       * select CDATA #IMPLIED
       * resultMap CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * notNullColumn CDATA #IMPLIED
       * columnPrefix CDATA #IMPLIED
       * resultSet CDATA #IMPLIED
       * foreignColumn CDATA #IMPLIED
       * autoMapping (true|false) #IMPLIED
       * fetchType (lazy|eager) #IMPLIED
       * >
       *
       * <!ELEMENT association (constructor?,id*,result*,association*,collection*, discriminator?)>
       * <!ATTLIST association
       * property CDATA #REQUIRED
       * column CDATA #IMPLIED
       * javaType CDATA #IMPLIED
       * jdbcType CDATA #IMPLIED
       * select CDATA #IMPLIED
       * resultMap CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * notNullColumn CDATA #IMPLIED
       * columnPrefix CDATA #IMPLIED
       * resultSet CDATA #IMPLIED
       * foreignColumn CDATA #IMPLIED
       * autoMapping (true|false) #IMPLIED
       * fetchType (lazy|eager) #IMPLIED
       * >
       *
       * <!ELEMENT discriminator (case+)>
       * <!ATTLIST discriminator
       * column CDATA #IMPLIED
       * javaType CDATA #REQUIRED
       * jdbcType CDATA #IMPLIED
       * typeHandler CDATA #IMPLIED
       * >
       *
       * <!ELEMENT case (constructor?,id*,result*,association*,collection*, discriminator?)>
       * <!ATTLIST case
       * value CDATA #REQUIRED
       * resultMap CDATA #IMPLIED
       * resultType CDATA #IMPLIED
       * >
       *
       * 从DTD的复杂程度就可知，resultMap相当于前面的cache/parameterMap等来说，是相当灵活的。下面我们来看resultMap在运行时到底是如何表示的。
       */
      resultMapElements(context.evalNodes("/mapper/resultMap"));

      /**
       * 解析sql片段
       */
      sqlElement(context.evalNodes("/mapper/sql"));
      /**
       * crud的解析从buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));
       * 语句开始，透过调用调用链，我们可以得知SQL语句的解析主要在XMLStatementBuilder中实现。
       */
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  /**
   * 在看具体实现之前，我们先大概看下DTD的定义（因为INSERT/UPDATE/DELETE属于一种类型，所以我们以INSERT为例），这样我们就知道整体关系和脉络：
   *
   * <!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
   * <!ATTLIST select
   * id CDATA #REQUIRED
   * parameterMap CDATA #IMPLIED
   * parameterType CDATA #IMPLIED
   * resultMap CDATA #IMPLIED
   * resultType CDATA #IMPLIED
   * resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE) #IMPLIED
   * statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
   * fetchSize CDATA #IMPLIED
   * timeout CDATA #IMPLIED
   * flushCache (true|false) #IMPLIED
   * useCache (true|false) #IMPLIED
   * databaseId CDATA #IMPLIED
   * lang CDATA #IMPLIED
   * resultOrdered (true|false) #IMPLIED
   * resultSets CDATA #IMPLIED
   * >
   *
   * <!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
   * <!ATTLIST insert
   * id CDATA #REQUIRED
   * parameterMap CDATA #IMPLIED
   * parameterType CDATA #IMPLIED
   * timeout CDATA #IMPLIED
   * flushCache (true|false) #IMPLIED
   * statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
   * keyProperty CDATA #IMPLIED
   * useGeneratedKeys (true|false) #IMPLIED
   * keyColumn CDATA #IMPLIED
   * databaseId CDATA #IMPLIED
   * lang CDATA #IMPLIED
   * >
   *
   * <!ELEMENT selectKey (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
   * <!ATTLIST selectKey
   * resultType CDATA #IMPLIED
   * statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
   * keyProperty CDATA #IMPLIED
   * keyColumn CDATA #IMPLIED
   * order (BEFORE|AFTER) #IMPLIED
   * databaseId CDATA #IMPLIED
   * >
   *
   *
   *
   * @param list
   * @param requiredDatabaseId
   */
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

  private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

  private void parsePendingCacheRefs() {
    Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
    synchronized (incompleteCacheRefs) {
      Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolveCacheRef();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Cache ref is still missing a resource...
        }
      }
    }
  }

  private void parsePendingStatements() {
    Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
    synchronized (incompleteStatements) {
      Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().parseStatementNode();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Statement is still missing a resource...
        }
      }
    }
  }

  private void cacheRefElement(XNode context) {
    if (context != null) {
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

  private void cacheElement(XNode context) {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

  private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // 在内部实现中将未完成的元素添加到configuration.incomplete中了
        // ignore, it will be retried
      }
    }
  }

  private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      //遍历构造器元素很简单
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
        //鉴别器discriminator的解析
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        //最后调用buildResultMappingFromContext建立具体的resultMap。buildResultMappingFromContext是个公共工具方法，会被反复使用
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

  protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
    if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      String property = resultMapNode.getStringAttribute("property");
      if (property != null && enclosingType != null) {
        MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
        return metaResultType.getSetterType(property);
      }
    } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      return enclosingType;
    }
    return null;
  }

  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<>();
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }

  /**
   * 鉴别器discriminator的解析
   *
   * 鉴别器非常容易理解,它的表现很像Java语言中的switch语句。
   * 定义鉴别器也是通过column和javaType属性来唯一标识，
   * column是用来确定某个字段是否为鉴别器， JavaType是需要被用来保证等价测试的合适类型
   *
   * <discriminator javaType="int" column="vehicle_type">
   *     <case value="1" resultMap="carResult"/>
   *     <case value="2" resultMap="truckResult"/>
   *     <case value="3" resultMap="vanResult"/>
   *     <case value="4" resultMap="suvResult"/>
   * </discriminator>
   * 如果 vehicle_type=1, 那就会使用下列这个结果映射。
   * <resultMap id="carResult" type="Car">
   *   <result property="doorCount" column="door_count" />
   * </resultMap>
   *
   * 其逻辑和之前处理构造器的时候类似，同样的使用build建立鉴别器并返回鉴别器实例，鉴别器中也可以嵌套association和collection。
   * 他们的实现逻辑和常规resultMap中的处理方式完全相同，这里就不展开重复讲。
   * 和构造器不一样的是，鉴别器中包含了case分支和对应的resultMap的映射
   *
   * @param context
   * @param resultType
   * @param resultMappings
   * @return
   * @throws Exception
   */
  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
      discriminatorMap.put(value, resultMap);
    }
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }

  /**
   * sql元素可以被用来定义可重用的SQL代码段，包含在其他语句中。
   *
   * 比如，他常被用来定义重用的列
   * <sql id=”userColumns”> id,username,password </sql>
   *
   * mybatis可以根据不同的数据库执行不同的sql，这就是通过sql元素上的databaseId属性来区别的。
   * 同样，首先设置sql元素的id，它必须是当前mapper文件所定义的命名空间。
   * sql元素本身的处理很简单，只是简单的过滤出databaseId和当前加载的配置文件相同的语句以备以后再解析crud遇到时进行引用。
   * 之所以不进行解析，是因为首先能够作为sql元素子元素的所有节点都可以作为crud的子元素，
   * 而且sql元素不会在运行时单独使用，所以也没有必要专门解析一番。
   * 下面我们重点来看crud的解析与内部表示。
   * @param list
   */
  private void sqlElement(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        sqlFragments.put(id, context);
      }
    }
  }

  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      return requiredDatabaseId.equals(databaseId);
    }
    if (databaseId != null) {
      return false;
    }
    if (!this.sqlFragments.containsKey(id)) {
      return true;
    }
    // skip this fragment if there is a previous one with a not null databaseId
    XNode context = this.sqlFragments.get(id);
    return context.getStringAttribute("databaseId") == null;
  }

  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");

    /**
     *
     * resultMap中可以包含association或collection复合类型,这些复合类型可以使用外部定义的公用resultMap或者内嵌resultMap,
     * 所以这里的处理逻辑是如果有resultMap就获取resultMap,如果没有,那就动态生成一个。
     * 如果自动生成的话，他的resultMap id通过调用XNode.getValueBasedIdentifier()来获得
     *
     * <resultMap id="blogResult" type="Blog">
     *   <id property="id" column="blog_id" />
     *   <result property="title" column="blog_title"/>
     *   <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
     *   <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
     * </resultMap>
     *
     * 其中唯一值得注意的是processNestedResultMappings，它用于解析包含的association或collection复合类型,这些复合类型可以使用外部定义的公用resultMap或者内嵌resultMap,
     * 所以这里的处理逻辑是如果是外部resultMap就获取对应resultMap的名称,如果没有,那就动态生成一个。
     * 如果自动生成的话，其resultMap id通过调用XNode.getValueBasedIdentifier()来获得。由于colletion和association、discriminator里面还可以包含复合类型，
     * 所以将进行递归解析直到所有的子元素都为基本列位置，它在使用层面的目的在于将关系模型映射为对象树模型
     *
     * 注意“ofType”属性，这个属性用来区分JavaBean(或字段)属性类型和集合中存储的对象类型。
     */
    String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections.emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    //得到各属性之后，调用builderAssistant.buildResultMapping最后创建ResultMap。
    // 其中除了 javaType,column外，其他都是可选的，property也就是中的name属性或者中的property属性，
    // 主要用于根据@Param或者jdk 8 -parameters形参名而非依赖声明顺序进行映射。
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

  /**
   * 对于其中的每个非select属性映射，调用resultMapElement进行递归解析
   *
   * select的用途在于指定另外一个映射语句的ID,加载这个属性映射需要的复杂类型。
   * 在列属性中指定的列的值将被传递给目标 select 语句作为参数。
   * 在上面的例子中，id的值会作为selectPostsForBlog的参数，这个语句会为每条映射到blogResult的记录执行一次selectPostsForBlog，
   * 并将返回的值添加到blog.posts属性中，其类型为Post。
   * @param context
   * @param resultMappings
   * @param enclosingType
   * @return
   * @throws Exception
   */
  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) throws Exception {
    if ("association".equals(context.getName())
        || "collection".equals(context.getName())
        || "case".equals(context.getName())) {
      if (context.getStringAttribute("select") == null) {
        validateCollection(context, enclosingType);
        ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
        return resultMap.getId();
      }
    }
    return null;
  }

  protected void validateCollection(XNode context, Class<?> enclosingType) {
    if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
        && context.getStringAttribute("javaType") == null) {
      MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
      String property = context.getStringAttribute("property");
      if (!metaResultType.hasSetter(property)) {
        throw new BuilderException(
          "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
      }
    }
  }

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      //判断namespace的value是否为一个类，如果是一个类将命名空间和Mapper代理类，添加到MapperProxyFactory代理类集合中
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

}
